#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
语音转文字模块
Speech to Text Module

基于讯飞语音识别API的语音转文字功能
Speech to text functionality based on iFlytek Speech Recognition API
"""

import websocket
import datetime
import hashlib
import base64
import hmac
import json
from urllib.parse import urlencode
import time
import ssl
from wsgiref.handlers import format_date_time
from datetime import datetime
from time import mktime
import _thread as thread
import logging
from typing import Optional, Dict, Any, Callable
from queue import Queue
import os
import tempfile
import subprocess
import shutil

# 帧状态常量
STATUS_FIRST_FRAME = 0  # 第一帧的标识
STATUS_CONTINUE_FRAME = 1  # 中间帧标识
STATUS_LAST_FRAME = 2  # 最后一帧的标识


class SpeechToTextClient:
    """
    语音转文字客户端
    Speech to Text Client
    
    基于讯飞语音识别WebSocket API的语音转文字功能
    Speech to text functionality based on iFlytek WebSocket API
    """
    
    def __init__(self, config: Dict[str, Any]):
        """
        初始化语音转文字客户端
        Initialize speech to text client
        
        Args:
            config (Dict[str, Any]): 配置参数 / Configuration parameters
                - app_id: 应用ID / Application ID
                - api_key: API密钥 / API Key
                - api_secret: API密钥 / API Secret
                - domain: 识别领域 / Recognition domain (default: "iat")
                - language: 识别语言 / Recognition language (default: "zh_cn")
                - accent: 口音 / Accent (default: "mandarin")
                - vad_eos: 静音检测时间 / VAD end of speech time (default: 10000)
        """
        self.config = config
        self.app_id = config.get('app_id', '')
        self.api_key = config.get('api_key', '')
        self.api_secret = config.get('api_secret', '')
        
        # 业务参数
        self.domain = config.get('domain', 'iat')
        self.language = config.get('language', 'zh_cn')
        self.accent = config.get('accent', 'mandarin')
        self.vad_eos = config.get('vad_eos', 10000)
        
        # 设置日志
        self.logger = logging.getLogger(__name__)
        
        # API密钥配置完成
        self.logger.info("Speech to text client initialized with provided credentials")
        
        # 识别结果
        self.recognition_result = ""
        self.result_queue = Queue()
        self.error_queue = Queue()
        
        # WebSocket连接
        self.ws = None
        self.is_connected = False
        
        # 回调函数
        self.on_result_callback: Optional[Callable[[str], None]] = None
        self.on_error_callback: Optional[Callable[[str, int], None]] = None
        
        # 音频转换相关
        self.temp_files = []  # 临时文件列表
        
        # 添加对象销毁标志
        self._is_destroyed = False
    
    def __del__(self):
        """
        析构方法，确保资源被正确清理
        Destructor to ensure proper resource cleanup
        """
        try:
            self.destroy()
        except Exception as e:
            # 在析构函数中不应该抛出异常
            pass
    
    def destroy(self):
        """
        手动销毁对象并清理所有资源
        Manually destroy object and cleanup all resources
        """
        if self._is_destroyed:
            return
            
        self._is_destroyed = True
        
        try:
            # 关闭WebSocket连接
            if hasattr(self, 'ws') and self.ws:
                try:
                    self.ws.close()
                except Exception:
                    pass
                self.ws = None
            
            # 清理队列
            if hasattr(self, 'result_queue') and self.result_queue:
                try:
                    while not self.result_queue.empty():
                        self.result_queue.get_nowait()
                except Exception:
                    pass
                    
            if hasattr(self, 'error_queue') and self.error_queue:
                try:
                    while not self.error_queue.empty():
                        self.error_queue.get_nowait()
                except Exception:
                    pass
            
            # 清理临时文件
            self.cleanup()
            
            # 清理回调函数引用
            self.on_result_callback = None
            self.on_error_callback = None
            
            self.logger.info("SpeechToTextClient destroyed successfully")
            
        except Exception as e:
            if hasattr(self, 'logger'):
                self.logger.error(f"Error during destruction: {e}")
    
    def set_callbacks(self, 
                     on_result: Optional[Callable[[str], None]] = None,
                     on_error: Optional[Callable[[str, int], None]] = None):
        """
        设置回调函数
        Set callback functions
        
        Args:
            on_result: 识别结果回调 / Recognition result callback
            on_error: 错误回调 / Error callback
        """
        self.on_result_callback = on_result
        self.on_error_callback = on_error
    
    def _check_ffmpeg(self):
        """
        检查FFmpeg是否可用
        Check if FFmpeg is available
        
        Returns:
            bool: FFmpeg是否可用 / Whether FFmpeg is available
        """
        try:
            result = subprocess.run(['ffmpeg', '-version'], 
                                  capture_output=True, text=True, timeout=10)
            return result.returncode == 0
        except (subprocess.TimeoutExpired, FileNotFoundError, subprocess.SubprocessError):
            return False
    
    def _convert_to_pcm(self, audio_file):
        """
        将音频文件转换为PCM格式
        Convert audio file to PCM format
        
        Args:
            audio_file (str): 输入音频文件路径 / Input audio file path
            
        Returns:
            str: 转换后的PCM文件路径 / Converted PCM file path
        """
        if not self._check_ffmpeg():
            self.logger.warning("FFmpeg not available, assuming input is already in correct format")
            return audio_file
        
        # 检查文件扩展名
        file_ext = os.path.splitext(audio_file)[1].lower()
        
        if file_ext == '.mp3':
            # MP3文件直接使用，API支持
            self.logger.info(f"Using MP3 file directly: {audio_file}")
            return audio_file
        
        # 其他格式转换为PCM
        temp_dir = tempfile.gettempdir()
        temp_filename = f"converted_audio_{int(time.time())}.pcm"
        output_file = os.path.join(temp_dir, temp_filename)
        
        try:
            # 使用FFmpeg转换为16kHz, 16bit, 单声道的PCM格式
            cmd = [
                'ffmpeg', '-y',             # 覆盖输出文件
                '-i', audio_file,           # 输入文件
                '-ar', '16000',             # 采样率16kHz
                '-acodec', 'pcm_s16le',     # 16bit PCM编码
                '-ac', '1',                 # 单声道
                '-f', 's16le',              # 输出格式为原始PCM
                '-loglevel', 'error',       # 减少日志输出
                output_file
            ]
            
            self.logger.info(f"Converting audio: {' '.join(cmd)}")
            
            # 使用更安全的subprocess调用方式
            try:
                result = subprocess.run(
                    cmd, 
                    capture_output=True, 
                    text=True, 
                    timeout=30,
                    creationflags=subprocess.CREATE_NO_WINDOW if hasattr(subprocess, 'CREATE_NO_WINDOW') else 0
                )
            except subprocess.TimeoutExpired as timeout_error:
                self.logger.error(f"FFmpeg conversion timeout: {timeout_error}")
                # 尝试清理可能创建的部分文件
                try:
                    if os.path.exists(output_file):
                        os.remove(output_file)
                except Exception:
                    pass
                return audio_file
            except Exception as subprocess_error:
                self.logger.error(f"FFmpeg subprocess error: {subprocess_error}")
                return audio_file
            
            if result.returncode != 0:
                self.logger.error(f"FFmpeg conversion failed (return code: {result.returncode}): {result.stderr}")
                # 清理可能创建的部分文件
                try:
                    if os.path.exists(output_file):
                        os.remove(output_file)
                except Exception:
                    pass
                return audio_file  # 转换失败，使用原文件
            
            # 验证输出文件是否成功创建且有内容
            if not os.path.exists(output_file):
                self.logger.error(f"FFmpeg output file not created: {output_file}")
                return audio_file
            
            if os.path.getsize(output_file) == 0:
                self.logger.error(f"FFmpeg output file is empty: {output_file}")
                try:
                    os.remove(output_file)
                except Exception:
                    pass
                return audio_file
            
            # 记录临时文件以便后续清理
            if hasattr(self, 'temp_files'):
                self.temp_files.append(output_file)
            else:
                self.temp_files = [output_file]
            
            self.logger.info(f"Audio converted successfully to: {output_file} (size: {os.path.getsize(output_file)} bytes)")
            return output_file
            
        except Exception as e:
            self.logger.error(f"Audio conversion error: {e}")
            # 清理可能创建的部分文件
            try:
                if os.path.exists(output_file):
                    os.remove(output_file)
            except Exception:
                pass
            return audio_file
    
    def cleanup(self):
        """
        清理临时文件
        Clean up temporary files
        """
        if not hasattr(self, 'temp_files') or not self.temp_files:
            return
            
        # 创建临时文件列表的副本，避免在迭代过程中修改原列表
        temp_files_copy = self.temp_files.copy()
        
        for temp_file in temp_files_copy:
            try:
                if temp_file and isinstance(temp_file, str) and os.path.exists(temp_file):
                    # 检查文件是否被占用
                    try:
                        # 尝试以独占模式打开文件来检查是否被占用
                        with open(temp_file, 'r+b') as f:
                            pass
                        # 如果成功打开，则可以安全删除
                        os.remove(temp_file)
                        self.logger.info(f"Cleaned up temporary file: {temp_file}")
                    except (PermissionError, OSError) as perm_error:
                        # 文件被占用或权限不足，稍后重试
                        self.logger.warning(f"File {temp_file} is in use, will retry: {perm_error}")
                        time.sleep(0.1)
                        try:
                            if os.path.exists(temp_file):
                                os.remove(temp_file)
                                self.logger.info(f"Cleaned up temporary file on retry: {temp_file}")
                        except Exception as retry_error:
                            self.logger.warning(f"Failed to clean up {temp_file} on retry: {retry_error}")
                elif temp_file:
                    self.logger.debug(f"Temporary file already removed or invalid: {temp_file}")
            except Exception as e:
                self.logger.warning(f"Failed to clean up {temp_file}: {e}")
        
        # 安全清空临时文件列表
        try:
            self.temp_files.clear()
        except Exception as clear_error:
            self.logger.warning(f"Error clearing temp_files list: {clear_error}")
            # 重新初始化列表
            self.temp_files = []
        
    def _create_url(self):
        """
        生成WebSocket连接URL
        Generate WebSocket connection URL
        
        Returns:
            str: WebSocket URL
        """
        url = 'wss://ws-api.xfyun.cn/v2/iat'
        
        # 生成RFC1123格式的时间戳
        now = datetime.now()
        date = format_date_time(mktime(now.timetuple()))
        
        # 拼接字符串
        signature_origin = "host: " + "ws-api.xfyun.cn" + "\n"
        signature_origin += "date: " + date + "\n"
        signature_origin += "GET " + "/v2/iat " + "HTTP/1.1"
        
        # 进行hmac-sha256进行加密
        signature_sha = hmac.new(self.api_secret.encode('utf-8'), 
                               signature_origin.encode('utf-8'),
                               digestmod=hashlib.sha256).digest()
        signature_sha = base64.b64encode(signature_sha).decode(encoding='utf-8')
        
        authorization_origin = "api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"" % (
            self.api_key, "hmac-sha256", "host date request-line", signature_sha)
        authorization = base64.b64encode(authorization_origin.encode('utf-8')).decode(encoding='utf-8')
        
        # 将请求的鉴权参数组合为字典
        v = {
            "authorization": authorization,
            "date": date,
            "host": "ws-api.xfyun.cn"
        }
        
        # 拼接鉴权参数，生成url
        url = url + '?' + urlencode(v)
        return url
    
    def _on_message(self, ws, message):
        """
        处理WebSocket消息
        Handle WebSocket message
        """
        if self._is_destroyed:
            return
            
        try:
            if not message:
                self.logger.warning("Received empty message")
                return
                
            response = json.loads(message)
            code = response.get("code", -1)
            sid = response.get("sid", "")
            
            if code != 0:
                err_msg = response.get("message", "Unknown error")
                self.logger.error(f"Recognition error - sid:{sid}, code:{code}, message:{err_msg}")
                
                # 安全地添加到错误队列
                try:
                    if hasattr(self, 'error_queue') and self.error_queue:
                        self.error_queue.put((err_msg, code))
                except Exception as queue_error:
                    self.logger.error(f"Error adding to error queue: {queue_error}")
                
                # 安全地调用错误回调
                try:
                    if self.on_error_callback and callable(self.on_error_callback):
                        self.on_error_callback(err_msg, code)
                except Exception as callback_error:
                    self.logger.error(f"Error in error callback: {callback_error}")
            else:
                # 解析识别结果
                try:
                    data = response.get("data", {}).get("result", {}).get("ws", [])
                    result = ""
                    for item in data:
                        for word in item.get("cw", []):
                            result += word.get("w", "")
                    
                    if result:
                        self.recognition_result += result
                        
                        # 安全地添加到结果队列
                        try:
                            if hasattr(self, 'result_queue') and self.result_queue:
                                self.result_queue.put(result)
                        except Exception as queue_error:
                            self.logger.error(f"Error adding to result queue: {queue_error}")
                        
                        self.logger.info(f"Recognition result: {result}")
                        
                        # 安全地调用结果回调
                        try:
                            if self.on_result_callback and callable(self.on_result_callback):
                                self.on_result_callback(result)
                        except Exception as callback_error:
                            self.logger.error(f"Error in result callback: {callback_error}")
                            
                except Exception as parse_error:
                    self.logger.error(f"Error parsing recognition result: {parse_error}")
                        
        except json.JSONDecodeError as json_error:
            self.logger.error(f"Invalid JSON message: {json_error}")
            try:
                if hasattr(self, 'error_queue') and self.error_queue:
                    self.error_queue.put((f"Invalid JSON: {str(json_error)}", -1))
            except Exception:
                pass
        except Exception as e:
            self.logger.error(f"Error parsing message: {e}")
            try:
                if hasattr(self, 'error_queue') and self.error_queue:
                    self.error_queue.put((str(e), -1))
            except Exception:
                pass
    
    def _on_error(self, ws, error):
        """
        处理WebSocket错误
        Handle WebSocket error
        """
        if self._is_destroyed:
            return
            
        self.logger.error(f"WebSocket error: {error}")
        
        # 安全地添加到错误队列
        try:
            if hasattr(self, 'error_queue') and self.error_queue:
                self.error_queue.put((str(error), -1))
        except Exception as queue_error:
            self.logger.error(f"Error adding WebSocket error to queue: {queue_error}")
        
        # 安全地调用错误回调
        try:
            if self.on_error_callback and callable(self.on_error_callback):
                self.on_error_callback(str(error), -1)
        except Exception as callback_error:
            self.logger.error(f"Error in WebSocket error callback: {callback_error}")
    
    def _on_close(self, ws, close_status_code, close_msg):
        """
        处理WebSocket关闭
        Handle WebSocket close
        """
        if not self._is_destroyed:
            self.is_connected = False
            self.logger.info(f"WebSocket connection closed (code: {close_status_code}, msg: {close_msg})")
        
        # 安全地清理WebSocket引用
        try:
            if hasattr(self, 'ws'):
                self.ws = None
        except Exception as cleanup_error:
            self.logger.error(f"Error cleaning up WebSocket reference: {cleanup_error}")
    
    def _on_open(self, ws):
        """
        处理WebSocket连接建立
        Handle WebSocket connection open
        """
        self.is_connected = True
        self.logger.info("WebSocket connection established")
    
    def recognize_audio_file(self, audio_file_path: str, 
                           frame_size: int = 8000, 
                           interval: float = 0.04) -> str:
        """
        识别音频文件
        Recognize audio file
        
        Args:
            audio_file_path (str): 音频文件路径 / Audio file path
            frame_size (int): 每帧音频大小 / Frame size (default: 8000)
            interval (float): 发送间隔(秒) / Send interval in seconds (default: 0.04)
            
        Returns:
            str: 识别结果 / Recognition result
        """
        # 重置识别结果和连接状态
        self.recognition_result = ""
        self.is_connected = False
        self.ws = None
        
        # 检查音频文件是否存在
        if not os.path.exists(audio_file_path):
            self.logger.error(f"Audio file not found: {audio_file_path}")
            return ""
        
        # 转换音频格式
        converted_audio_path = self._convert_to_pcm(audio_file_path)
        
        try:
            # 创建WebSocket连接
            websocket.enableTrace(False)
            ws_url = self._create_url()
            
            # 公共参数和业务参数
            common_args = {"app_id": self.app_id}
            business_args = {
                "domain": self.domain,
                "language": self.language,
                "accent": self.accent,
                "vinfo": 1,
                "vad_eos": self.vad_eos
            }
            
            def run_recognition():
                """运行识别任务"""
                status = STATUS_FIRST_FRAME
                
                try:
                    # 检查WebSocket连接状态
                    if not self.ws:
                        self.logger.error("WebSocket connection not available")
                        return
                    
                    # 检查转换后的音频文件是否存在
                    if not os.path.exists(converted_audio_path):
                        self.logger.error(f"Converted audio file not found: {converted_audio_path}")
                        return
                    
                    with open(converted_audio_path, "rb") as fp:
                        while True:
                            try:
                                buf = fp.read(frame_size)
                                
                                # 文件结束
                                if not buf:
                                    status = STATUS_LAST_FRAME
                                
                                # 构建数据包
                                if status == STATUS_FIRST_FRAME:
                                    # 第一帧，包含业务参数
                                    data = {
                                        "common": common_args,
                                        "business": business_args,
                                        "data": {
                                            "status": 0,
                                            "format": "audio/L16;rate=16000",
                                            "audio": str(base64.b64encode(buf), 'utf-8'),
                                            "encoding": "raw"
                                        }
                                    }
                                    status = STATUS_CONTINUE_FRAME
                                elif status == STATUS_CONTINUE_FRAME:
                                    # 中间帧
                                    data = {
                                        "data": {
                                            "status": 1,
                                            "format": "audio/L16;rate=16000",
                                            "audio": str(base64.b64encode(buf), 'utf-8'),
                                            "encoding": "raw"
                                        }
                                    }
                                elif status == STATUS_LAST_FRAME:
                                    # 最后一帧
                                    data = {
                                        "data": {
                                            "status": 2,
                                            "format": "audio/L16;rate=16000",
                                            "audio": str(base64.b64encode(buf), 'utf-8'),
                                            "encoding": "raw"
                                        }
                                    }
                                
                                # 检查WebSocket连接状态后再发送
                                if self.ws and hasattr(self.ws, 'send'):
                                    try:
                                        self.ws.send(json.dumps(data))
                                    except Exception as send_error:
                                        self.logger.error(f"Failed to send data: {send_error}")
                                        break
                                else:
                                    self.logger.error("WebSocket connection lost")
                                    break
                                
                                if status == STATUS_LAST_FRAME:
                                    time.sleep(1)  # 等待最后的识别结果
                                    break
                                
                                # 模拟音频采样间隔
                                time.sleep(interval)
                                
                            except Exception as read_error:
                                self.logger.error(f"Error reading audio data: {read_error}")
                                break
                        
                except Exception as e:
                    self.logger.error(f"Error during recognition: {e}")
                finally:
                    # 安全关闭WebSocket连接
                    try:
                        if self.ws and hasattr(self.ws, 'close'):
                            self.ws.close()
                    except Exception as close_error:
                        self.logger.error(f"Error closing WebSocket: {close_error}")
                    finally:
                        self.ws = None
            
            # 创建WebSocket连接
            try:
                self.ws = websocket.WebSocketApp(
                    ws_url,
                    on_message=self._on_message,
                    on_error=self._on_error,
                    on_close=self._on_close,
                    on_open=lambda ws: thread.start_new_thread(run_recognition, ())
                )
                
                # 运行WebSocket
                self.ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
                
            except Exception as ws_error:
                self.logger.error(f"WebSocket connection error: {ws_error}")
                return ""
            
            return self.recognition_result
        
        except Exception as main_error:
            self.logger.error(f"Recognition process error: {main_error}")
            return ""
        
        finally:
            # 确保WebSocket连接被正确关闭
            try:
                if self.ws and hasattr(self.ws, 'close'):
                    self.ws.close()
            except Exception as cleanup_ws_error:
                self.logger.error(f"Error closing WebSocket in cleanup: {cleanup_ws_error}")
            finally:
                self.ws = None
                
            # 清理临时文件
            try:
                self.cleanup()
            except Exception as cleanup_error:
                self.logger.error(f"Error during cleanup: {cleanup_error}")
    
    def get_latest_result(self) -> Optional[str]:
        """
        获取最新的识别结果
        Get latest recognition result
        
        Returns:
            Optional[str]: 最新识别结果 / Latest recognition result
        """
        try:
            return self.result_queue.get_nowait()
        except:
            return None
    
    def get_latest_error(self) -> Optional[tuple]:
        """
        获取最新的错误信息
        Get latest error information
        
        Returns:
            Optional[tuple]: (错误消息, 错误代码) / (error message, error code)
        """
        try:
            return self.error_queue.get_nowait()
        except:
            return None
    
    def clear_results(self):
        """
        清空识别结果
        Clear recognition results
        """
        self.recognition_result = ""
        while not self.result_queue.empty():
            try:
                self.result_queue.get_nowait()
            except:
                break
        
        while not self.error_queue.empty():
            try:
                self.error_queue.get_nowait()
            except:
                break